#pragma once

#include <spdlog/spdlog.h>
#include <mysql_driver.h>
#include <mysql_connection.h>
#include <queue>
#include <mutex>
#include <condition_variable>
#include "../util/config.hh"
#include "../util/except.hh"

namespace WordMemory
{
    class DbConnectionPool
    {
    public:
        static DbConnectionPool& instance()
        {
            static DbConnectionPool pool;
            return pool;
        }

        std::unique_ptr<sql::Connection> acquire()
        {
            std::unique_lock<std::mutex> lock(_mutex);
            _cond.wait(lock, [this]() { return !_connections.empty(); });

            auto conn = std::move(_connections.front());
            _connections.pop();
            lock.unlock();

            spdlog::debug("Acquired database connection");
            return conn;
        }

        void release(std::unique_ptr<sql::Connection> conn)
        {
            std::lock_guard<std::mutex> lock(_mutex);
            _connections.push(std::move(conn));
            spdlog::debug("Released database connection");
            _cond.notify_one();
        }

    private:
        DbConnectionPool()
        {
            sql::mysql::MySQL_Driver* driver = sql::mysql::get_mysql_driver_instance();
            for (int i = 0; i < _pool_size; ++i)
            {
                try
                {
                    auto conn = std::unique_ptr<sql::Connection>(driver->connect(
                        "tcp://" + Config::instance().sql_host() + ":" + std::to_string(Config::instance().sql_port()),
                        Config::instance().sql_user(),
                        Config::instance().sql_passwd()));
                    conn->setSchema(Config::instance().sql_db());
                    _connections.push(std::move(conn));
                }
                catch (const sql::SQLException& e)
                {
                    spdlog::error("Failed to create connection for pool: {}", e.what());
                    throw UtilExcept("Failed to initialize connection pool");
                }
            }
            spdlog::info("Database connection pool initialized with {} connections", _pool_size);
        }

        DbConnectionPool(const DbConnectionPool&) = delete;
        DbConnectionPool& operator=(const DbConnectionPool&) = delete;

    private:
        std::queue<std::unique_ptr<sql::Connection>> _connections;
        std::mutex _mutex;
        std::condition_variable _cond;
        int _pool_size = 10;
    };

    class ConnectionGuard
    {
    public:
        ConnectionGuard()
            : _conn(DbConnectionPool::instance().acquire())
        {}

        ~ConnectionGuard()
        {
            if (_conn)
                DbConnectionPool::instance().release(std::move(_conn));
        }

        sql::Connection* get() const
        {
            return _conn.get();
        }

        ConnectionGuard(const ConnectionGuard&) = delete;
        ConnectionGuard& operator=(const ConnectionGuard&) = delete;

        ConnectionGuard(ConnectionGuard&& other) noexcept
            : _conn(std::move(other._conn))
        {
            other._conn = nullptr;
        }

        ConnectionGuard& operator=(ConnectionGuard&& other) noexcept
        {
            if (this != &other)
            {
                if (_conn)
                    DbConnectionPool::instance().release(std::move(_conn));

                _conn = std::move(other._conn);
                other._conn = nullptr;
            }
            return *this;
        }

    private:
        std::unique_ptr<sql::Connection> _conn;
    };
}